Skip to content

Conversation

@joshua-salsadigital
Copy link
Collaborator

@joshua-salsadigital joshua-salsadigital commented Oct 29, 2025

https://www.drupal.org/project/civictheme/issues/3517035

Checklist before requesting a review

  • I have formatted the subject to include ticket number as Issue #123456 by drupal_org_username: Issue title
  • I have added a link to the issue tracker
  • I have provided information in Changed section about WHY something was done if this was not a normal implementation
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added tests that prove my fix is effective or that my feature works
  • I have run new and existing relevant tests locally with my changes, and they passed
  • I have provided screenshots, where applicable

Changed

Screenshots

Summary by CodeRabbit

  • Bug Fixes
    • Error summaries now automatically receive focus after page load or AJAX updates, improving keyboard and screen reader accessibility.
    • The rendered error messages include focusable attributes so assistive technologies are reliably directed to the first error.

@joshua-salsadigital joshua-salsadigital self-assigned this Oct 29, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 29, 2025

Walkthrough

A Drupal accessibility enhancement adds behavior to focus the first error-summary element after page load or AJAX updates. Two JavaScript behavior files implement the focus logic; the Twig status-messages template now emits unique IDs and tabindex attributes for error messages.

Changes

Cohort / File(s) Change Summary
JavaScript Behavior Implementation
web/themes/contrib/civictheme/assets/js/status-messages/status-messages.js, web/themes/contrib/civictheme/civictheme_starter_kit/assets/js/status-messages/status-messages.js
Adds Drupal.behaviors.ctFocusErrorSummary with attach(context, settings) that waits ~30ms, finds the first element whose id starts with error-summary-, ensures it is focusable (tabindex="-1"), and focuses it.
Template Update
web/themes/contrib/civictheme/templates/misc/status-messages.html.twig
When message type is error, computes id_suffix from loop indices and sets id_attr (id and tabindex="-1"), passing it into the civictheme:message template so rendered error summaries receive the id and tabindex.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant Drupal
    participant Behavior as ctFocusErrorSummary
    participant DOM

    Browser->>Drupal: Page load or AJAX complete
    Drupal->>Behavior: Attach(ctFocusErrorSummary)
    Note right of Behavior: wait ~30ms to allow DOM updates
    Behavior->>DOM: querySelector('[id^="error-summary-"]')
    alt element found
        DOM-->>Behavior: element
        Behavior->>DOM: setAttribute('tabindex','-1')
        Behavior->>DOM: focus()
        DOM-->>Browser: focus moved to error summary
    else no element
        DOM-->>Behavior: null
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Review JS duplication across two files to ensure consistency and avoid drift.
  • Verify timing (30ms) is sufficient in AJAX-heavy contexts and doesn't cause race conditions.
  • Confirm Twig id construction produces unique, valid IDs and that aria/focus behavior meets accessibility guidelines.

Suggested labels

State: Needs review

Suggested reviewers

  • richardgaunt

Poem

🐰
I hopped through markup, soft and spry,
Gave error summaries a gentle eye.
With tabindex set and focus true,
Users see errors — clear and new.
A little rabbit's accessibility cue.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Issue #3517035 by richardgaunt, joshua1234511, fionamorrison23: WCAG - Focus lands in wrong place upon form submission when error present" clearly and specifically describes the accessibility issue being addressed. The changes across all three modified files implement a solution to this exact problem: by adding a new Drupal behavior that focuses on error summary elements and modifying the template to add proper id and tabindex attributes. The title is specific, non-vague, and follows the repository's naming conventions with issue number and contributor attribution. A teammate reviewing the repository history would immediately understand that this changeset addresses a WCAG compliance issue related to focus management.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/3517035

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c3b1bb and 46530fa.

📒 Files selected for processing (1)
  • web/themes/contrib/civictheme/templates/misc/status-messages.html.twig (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • web/themes/contrib/civictheme/templates/misc/status-messages.html.twig
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build-and-push
  • GitHub Check: build-and-push
  • GitHub Check: commit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
web/themes/contrib/civictheme/assets/js/status-messages/status-messages.js (3)

6-6: Document the rationale for the 30ms delay.

The 30ms timeout appears arbitrary. While a brief delay may be necessary to ensure DOM updates complete after AJAX operations, the specific value and reasoning should be documented.

Consider adding a more detailed comment:

-      // Wait for DOM update after AJAX or normal load
+      // Wait 30ms for DOM updates to complete after AJAX or initial load.
+      // This ensures error summaries are fully rendered before focusing.
       setTimeout(function () {

10-10: Redundant tabindex assignment.

Line 10 sets tabindex="-1" on the error element, but the Twig template (line 10 of status-messages.html.twig) already includes tabindex="-1" in the id_attr string. This assignment is redundant.

If the template reliably sets tabindex, this line can be removed:

         if ($error.length) {
-          $error.attr('tabindex', '-1'); // ensure focusable (in case)
           $error.focus();
         }

However, keep it as a defensive measure if there's uncertainty about the template's attribute handling.


3-15: Consider using Drupal's once() API to prevent duplicate processing.

The behavior will re-run on every AJAX update within the same context. While this is likely the desired behavior for form validation errors, using the once() API would prevent the same error element from being focused multiple times if the behavior attaches repeatedly to the same DOM.

 Drupal.behaviors.ctFocusErrorSummary = {
   attach: function (context, settings) {
     // Wait for DOM update after AJAX or normal load
     setTimeout(function () {
       // Find any element with id starting with 'error-summary-'
-      var $error = $('[id^="error-summary-"]', context).first();
+      var $error = $('[id^="error-summary-"]', context).once('ct-focus-error').first();
       if ($error.length) {
         $error.attr('tabindex', '-1'); // ensure focusable (in case)
         $error.focus();
       }
     }, 30);
   }
 };
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7369f58 and 6c3b1bb.

📒 Files selected for processing (3)
  • web/themes/contrib/civictheme/assets/js/status-messages/status-messages.js (1 hunks)
  • web/themes/contrib/civictheme/civictheme_starter_kit/assets/js/status-messages/status-messages.js (1 hunks)
  • web/themes/contrib/civictheme/templates/misc/status-messages.html.twig (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build-and-push
  • GitHub Check: build-and-push
  • GitHub Check: commit

Comment on lines +1 to +16
// CivicTheme status-messages.js
(function ($, Drupal) {
Drupal.behaviors.ctFocusErrorSummary = {
attach: function (context, settings) {
// Wait for DOM update after AJAX or normal load
setTimeout(function () {
// Find any element with id starting with 'error-summary-'
var $error = $('[id^="error-summary-"]', context).first();
if ($error.length) {
$error.attr('tabindex', '-1'); // ensure focusable (in case)
$error.focus();
}
}, 30);
}
};
})(jQuery, Drupal);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Critical: Code duplication between theme and starter kit.

This file is identical to web/themes/contrib/civictheme/assets/js/status-messages/status-messages.js. Having duplicate code in both the main theme and starter kit creates a maintenance burden where changes must be synchronized across both files.

Recommended approach:

Since this is accessibility-critical functionality, the main theme version should handle it. The starter kit should either:

  1. Option 1 (Preferred): Remove this file entirely and inherit the behavior from the parent theme.
  2. Option 2: Only include this file if the starter kit needs to customize or extend the behavior. If so, document why the override is necessary.

If duplication is intentional (e.g., for easier customization), add a comment explaining that this file mirrors the parent theme's version and must be kept in sync:

// This file mirrors the parent theme's status-messages.js
// Keep in sync with: web/themes/contrib/civictheme/assets/js/status-messages/status-messages.js
🤖 Prompt for AI Agents
web/themes/contrib/civictheme/civictheme_starter_kit/assets/js/status-messages/status-messages.js
lines 1-16: this file duplicates the identical accessibility-focused
status-messages.js in the parent civictheme, creating maintenance burden; remove
this file from the starter kit so the parent theme's script is used (preferred),
or if the starter kit must provide it, replace its contents with a short comment
stating it intentionally mirrors the parent file and include the parent filepath
for sync tracking, or alternatively keep the file only when adding custom
behavior and document the reason for the override.

content: message|render,
type: (type == 'status' or type == 'info') ? 'information' : type,
vertical_spacing: 'both',
attributes: id_attr
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Passing string to attributes may break rendering.

If the civictheme:message template expects an Attribute object or array for the attributes parameter, passing a plain HTML string will likely not render correctly or may be escaped as text rather than merged into the element's attributes.

🤖 Prompt for AI Agents
In web/themes/contrib/civictheme/templates/misc/status-messages.html.twig around
line 16, the template is passing a plain HTML string to the `attributes`
parameter which may break rendering; change the call so `attributes` is an
Attribute object or an associative array (e.g. an attributes object or array
with keys like "id") instead of a raw string, creating or merging the
Attribute/array before passing it to the template and ensuring the id is set as
an attribute (not concatenated HTML), so the rendering engine can properly merge
and render the attributes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants